Skip to content

fix(bin): stop false wedge alarms for deliberately stood-down workers - #3281

Open
AgardnerAU wants to merge 51 commits into
kunchenguid:mainfrom
AgardnerAU:fm/fm-stooddown-worker-false-wedge-alarms
Open

AgardnerAU wants to merge 51 commits into
kunchenguid:mainfrom
AgardnerAU:fm/fm-stooddown-worker-false-wedge-alarms

Conversation

@AgardnerAU

@AgardnerAU AgardnerAU commented Aug 29, 2026 •

Copy link
Copy Markdown

Intent

On 2026-10-13 the captain approved updating 11 open upstream PRs, including #3281 ("yes, update those 11 PR's"). The approved update: merge current upstream main into each PR, fix the conflicts, run the tests, look into red checks, push, and post a short note on each PR.

Context: A read-only check on 2026-10-13 found this PR CONFLICTING with current upstream main (head 9dc61a0d). Upstream kunchenguid/firstmate closes a PR after 14 days of AUTHOR silence; only a push or comment by the author resets that clock, and a stale close is irreversible. On 2026-09-18 the captain asked for every PR waiting on us in the kunchenguid repositories to be kept alive.

The PR is on branch fm/fm-stooddown-worker-false-wedge-alarms from the fork AgardnerAU/firstmate into kunchenguid:main. Reuse that branch and that PR; never replace them.

PR #3281 is "fix(bin): stop false wedge alarms for deliberately stood-down workers": a worker that firstmate deliberately stood down (no agent running, work preserved for a later relaunch) must not raise false wedge or stuck alarms, while genuine failures and vanished endpoints stay visible.

What Changed

  • Adds two fm-control verbs, stand-down and repair-worker-state, for tmux only. They use a new bin/fm-worker-state-lib.sh and an exact state/<id>.worker-state record. stand-down stops the agent for a held ship or scout task. It publishes the record only after the classifier proves the agent is gone. It refuses when the task has an in-flight no-mistakes run, when that check cannot be answered (the new branch-run verdict in bin/fm-nm-run-lib.sh), when a steering instruction is still waiting in the inbox, or when an already-dead agent has no paused:/captain-held: declaration. repair-worker-state changes the record only toward supervision: it clears a record that cannot be proven or that a live agent contradicts. It never creates a hold.
  • fm-watch skips pane-stale and wedge detection for a window only when the record says stood-down and the endpoint is still proven dead, so a live worker behind a stale record is still checked. fm-crew-state reports a proven hold as parked · source: worker-state. A live run still takes priority over the hold, and a vanished endpoint reports unknown. fm-send refuses input to a proven stood-down task, and fm-teardown removes the record.
  • fm-spawn --relaunch clears the record before it starts the replacement and restores it if the relaunch aborts. The control docs, architecture docs, configuration docs and script docs describe the new verbs. New tests cover the stand-down lifecycle and its refusals, relaunch from a stood-down worker, crew-state reporting, send refusal and the watcher exemption.

🤖 Generated with Claude Code

Risk Assessment

✅ Low: The only change since the earlier review rounds is a clean merge of upstream main: the one conflict (tests/fm-crew-state.test.sh) keeps both the branch's stood-down tests and main's Forgejo test and calls all of them. In bin/fm-spawn.sh, the worker-state restore on an aborted relaunch and main's secondmate busy-arm rework now sit together correctly.

Testing

All six targeted suites (fm-control, fm-crew-state, fm-control-relaunch, fm-send-inbox, fm-task-inbox and fm-watch-triage) finished with exit 0 and no failures. I then built a disposable lab home with a private tmux socket and a real Claude CLI worker. I drove the real fm-control, fm-crew-state, fm-send, fm-watch and fm-wake-drain scripts through eight live scenarios, and all eight passed. Two more scenarios were not driven live, so they are marked untested: relaunch from a stood-down task, and stand-down refusal while a no-mistakes run is in flight. The suites cover both. I stopped the lab tmux server and the orphaned lab watcher, and removed the lab's socket directory. The permission layer refused rm -rf on the lab home in $TMPDIR (/var/folders/.../T/fm-lab.J5cQV9), so that directory is still there. There is no UI surface, so the evidence is CLI transcripts and a pane capture.

  • Live validation: ✅ go - 8 of 10 scenarios driven live against the product
Scenario Result Live Evidence
Operator stands down a live, idle Claude worker; the agent stops, the pane stays, and an exact worker-state record is written ✅ pass live live-stand-down.log S2: rc=0 'stood-down t1', the pane command changed from 2.1.296 to zsh, and the record holds schema=1, task_id=t1, endpoint=fmlab:fm-t1, state=stood-down
Crew-state reports the stood-down task as parked, and a repeat stand-down does nothing more ✅ pass live live-stand-down.log S3/S3b: 'state: parked · source: worker-state'; 'already-stood-down t1'
Real watcher raises no stale or wedge alarm for the stood-down task but still alarms for a dead worker with no declaration ✅ pass live live-watch.log: no .hash-fmlab_fm-t1 file and no t1 stale wake in 4 cycles; 'stale: fmlab:fm-t2' was emitted and queued
fm-send refuses an instruction to a stood-down task and creates no unread inbox record ✅ pass live live-stand-down.log S4: rc=1 'task t1 deliberately has no worker'; inbox has nothing pending
Adversarial: stand-down refuses to turn a worker that is already dead, with no declared hold, into an intentional hold ✅ pass live live-stand-down.log S5: rc=1 'already dead without a stand-down record'; no record written; crew-state is not parked
A declared hold ('paused:' line) lets stand-down record a worker that is already dead ✅ pass live live-stand-down.log S6: 'stood-down t2', crew-state parked
Vanished endpoint behind a stood-down record stays visible as unknown, and repair clears the record ✅ pass live live-stand-down.log S7/S8: repair kept the record while the endpoint was dead ('intact'); after kill-window, crew-state reported 'stood down but its endpoint is gone'; repair returned 'cleared-unprove…
Adversarial: a live worker behind an old stood-down record is not hidden, is not refused input, and repair clears the record ✅ pass live live-stand-down.log S9: crew-state 'endpoint has a live worker'; fm-send rc=0 and Claude received the instruction (live-t2-pane-after-send.txt); repair returned 'cleared-live-worker'
Relaunch from a stood-down task restarts in the preserved worktree and clears only the declaration ⏸️ untested no The prior payload did not establish a live result. It was not driven live because that needs a full fm-spawn of a second Claude worker in the lab, and the previous run had already exceeded its time bu…
Stand-down refuses while the branch has an in-flight no-mistakes run, or while the run check cannot be answered ⏸️ untested no The prior payload did not establish a live result. A live test needs a real in-flight no-mistakes run registered for a lab repository. Starting one is a pipeline-control action that this gate phase mu…
Evidence: Live stand-down transcript (S1-S9)

Source: Live stand-down transcript (S1-S9)

# Live lab run (real claude 2.1.296 in private tmux socket fm-lab, disposable FM_HOME)
## S1 live idle claude worker: crew-state before stand-down
$ bin/fm-crew-state.sh t1
state: unknown · source: pane · harness state unavailable (unknown missing)
[rc=0]
pane command before: 2.1.296
## S2 stand-down of a live claude worker
$ bin/fm-control.sh t1 stand-down
fm-gate-refuse: gate agent lifecycle permitted only against lab home $LAB
stood-down t1 harness=claude backend=tmux endpoint=fmlab:fm-t1 worktree=$LAB/wt-t1
[rc=0]
pane command after: zsh
record state/t1.worker-state:
schema=1
task_id=t1
endpoint=fmlab:fm-t1
state=stood-down
## S3 crew-state after stand-down
$ bin/fm-crew-state.sh t1
state: parked · source: worker-state · worker deliberately stood down
[rc=0]
## S3b repeat stand-down is idempotent
$ bin/fm-control.sh t1 stand-down
fm-gate-refuse: gate agent lifecycle permitted only against lab home $LAB
already-stood-down t1 harness=claude backend=tmux endpoint=fmlab:fm-t1 worktree=$LAB/wt-t1
[rc=0]
## S4 fm-send refuses a stood-down worker
$ bin/fm-send.sh t1 hello
fm-gate-refuse: gate agent lifecycle permitted only against lab home $LAB
WARNING: watcher still down (same stale episode; last beat: never, grace 300s) - full banner already printed this episode.
error: task t1 deliberately has no worker; relaunch it before sending an instruction
[rc=1]
inbox pending: 
## S5 adversarial: already-dead worker with no declaration
$ bin/fm-control.sh t2 stand-down
fm-gate-refuse: gate agent lifecycle permitted only against lab home $LAB
error: task t2's agent is already dead without a stand-down record; refusing to relabel a possible worker failure as intentional. Declare the hold first by appending a 'paused: <reason>' line to $LAB/state/t2.status, then re-run stand-down
[rc=1]
t2 record exists? no
$ bin/fm-crew-state.sh t2
state: unknown · source: pane · harness state unavailable (unknown missing)
[rc=0]
## S6 declared hold path: paused line then stand-down of already-dead t2
$ bin/fm-control.sh t2 stand-down
fm-gate-refuse: gate agent lifecycle permitted only against lab home $LAB
stood-down t2 harness=claude backend=tmux endpoint=fmlab:fm-t2 worktree=$LAB/wt-t2
[rc=0]
$ bin/fm-crew-state.sh t2
state: parked · source: worker-state · worker deliberately stood down
[rc=0]
## S7 repair-worker-state keeps a valid record while endpoint is proven dead (no-op)
$ bin/fm-control.sh t1 repair-worker-state
fm-gate-refuse: gate agent lifecycle permitted only against lab home $LAB
intact agent-state=dead t1 harness=claude backend=tmux endpoint=fmlab:fm-t1 worktree=$LAB/wt-t1
[rc=0]
t1 record: state=stood-down
## S8 vanished endpoint: kill window fm-t1 behind the stood-down record
$ bin/fm-crew-state.sh t1
state: unknown · source: worker-state · stood down but its endpoint is gone: fmlab:fm-t1 (relaunch cannot restore it in place)
[rc=0]
$ bin/fm-control.sh t1 repair-worker-state
fm-gate-refuse: gate agent lifecycle permitted only against lab home $LAB
warning: task t1 declared no worker but its endpoint could not be proven dead; the declaration was cleared and the task is back under ordinary supervision
cleared-unproven-endpoint agent-state=missing t1 harness=claude backend=tmux endpoint=fmlab:fm-t1 worktree=$LAB/wt-t1
[rc=0]
t1 record exists after repair? no
$ bin/fm-crew-state.sh t1
state: unknown · source: pane · harness state unavailable (unknown missing)
[rc=0]
## S9 adversarial: live claude relaunched by hand in fm-t2 behind a still-present stood-down record
pane command: 2.1.296; record: state=stood-down
$ bin/fm-crew-state.sh t2
state: unknown · source: worker-state · record says stood down but endpoint has a live worker
[rc=0]
$ bin/fm-send.sh t2 Reply with the single word ok and nothing else.
fm-gate-refuse: gate agent lifecycle permitted only against lab home $LAB
[rc=0]
inbox: 001.msg handled 
$ bin/fm-control.sh t2 repair-worker-state
fm-gate-refuse: gate agent lifecycle permitted only against lab home $LAB
warning: task t2 declared no worker but its endpoint has a live agent; the declaration was cleared and the task is back under ordinary supervision
cleared-live-worker agent-state=alive t2 harness=claude backend=tmux endpoint=fmlab:fm-t2 worktree=$LAB/wt-t2
[rc=0]
t2 record exists after repair? no
Evidence: Live real watcher cycles (t1 never hashed or alarmed; t2 surfaced as stale)

Source: Live real watcher cycles (t1 never hashed or alarmed; t2 surfaced as stale)

# Live real bin/fm-watch.sh against lab home: t1 stood-down (record + zsh pane), t2 dead without declaration
## watcher cycle 1
stdout:
signal: $LAB/state/t1.status $LAB/state/t2.status
stale-hash files: 
drained:
1791928977	3	signal	t1.status	signal: $LAB/state/t1.status $LAB/state/t2.status
1791928977	4	signal	t2.status	signal: $LAB/state/t1.status $LAB/state/t2.status
wake annotation: latest wake-EVENT observed at drain, not current state: t1.status: working: live test
wake annotation: latest wake-EVENT observed at drain, not current state: t2.status: working: live test
## watcher cycle 2
stdout:
stale: fmlab:fm-t2
stale-hash files: .hash-fmlab_fm-t2 
drained:
1791928987	5	stale	fmlab:fm-t2	stale: fmlab:fm-t2
## watcher cycle 3
(still running after 40s - stopping)
stdout:
stale-hash files: .hash-fmlab_fm-t2 
drained:
## watcher cycle 4
stdout:
watcher: already running pid 79483
stale-hash files: .hash-fmlab_fm-t2 
drained:
Evidence: Live Claude pane after fm-send to a live worker behind an old record

Source: Live Claude pane after fm-send to a live worker behind an old record

▝▜██████▀  Opus 5.5 · Claude Max
 ▝▝   ▝▝   /private/var/folders/41/64hrmnwx11q5lw3l9d7zmfgr0000gn/T/fm-lab.J5cQV9/wt-t2 · /rc
❯ : Firstmate instruction waiting: list "$FM_TASK_INBOX"/*.msg in your 't2.inbox' steering inbox, read and act on each in numeric order, then mv each into its handled/.
⏺ Listing and read inbox messages
  ⎿  $ env | grep -i FM_; find /private/var/folders/41/64hrmnwx11q5lw3l9d7zmfgr0000gn/T/fm-lab.J5cQV9 -maxdepth 3 -name '*inbox*' 2>/dev/null
✢ Catapulting… (9s · ↓ 369 tokens · thought for 1s)
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
❯ 
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
  Ctx: 35.3k | Ctx(u) Used: 4.4% | In: 35.3k | Out: 196
  𖠰 wt-t2 | ⎇ fm/live-t2 | Model: Opus 5.5 | Session: <1m
  ⏵⏵ auto mode on (shift+tab to cycle) · ← 1 agent
Evidence: fm-control suite log

Source: fm-control suite log

ok - fm-control exit: every verified harness gets its own verified exit command
ok - fm-control interrupt: every verified harness gets its own verified key and repeat count
ok - fm-control Devin interrupt: second press only after the armed hint, then busy invalidated without fabricating idle
ok - fm-control Devin interrupt: an idle agent gets one Escape and reports not-running
ok - fm-control Devin exit: a busy record whose turn already ended never opens the revert picker
ok - fm-control Devin interrupt: a revert picker a press opened is closed with Escape, never Enter
ok - fm-control Devin: an open revert picker refuses every typed command
ok - fm-control interrupt: opencode needs a double Escape, claude a single one
ok - fm-control: a harness with no verified control mechanics is refused, not guessed at
ok - fm-control-lib: a recorded harness resolves to its verified adapter without guessing
ok - fm-control-lib: only a runtime's own recorded session has a relaunch resume form
ok - fm-control: prefixed recorded harnesses reach interrupt and exit mechanics
ok - fm-control-lib: the backend key matrix matches each adapter's real send-key surface
ok - fm-control-lib: adapter capability is per task kind, not per adapter alone
ok - fm-control interrupt: a backend that cannot deliver the harness's key refuses instead of sending another
ok - fm-control: a backend that cannot prove an agent stopped refuses exit, stand-down, and relaunch
ok - fm-control-lib: stop-proving verbs are gated on the backends that really classify agent state
ok - fm-control: worker-state verbs do not drive Herdr
ok - fm-control: ordinary Herdr relaunch remains admitted
ok - fm-control stand-down: a proven exit becomes an exact durable no-worker declaration
ok - fm-control stand-down: a self-stopping agent is a successful stop, not a refusal
ok - fm-control stand-down: an unexplained dead agent remains eligible for recovery
ok - fm-control stand-down: an in-flight validation run keeps its worker, and is never cancelled for the operator
ok - fm-control stand-down: only an in-flight run blocks the hold
ok - fm-control stand-down: run attribution uses every source, not just axi status
ok - fm-control stand-down: an unanswerable run check refuses and names itself
ok - fm-control stand-down: malformed active branch identity fails closed
ok - fm-control stand-down: an absent run CLI licenses the hold, an unanswered one does not
ok - fm-control stand-down: a project with no run registration is held, while a silent failure still refuses
ok - fm-control stand-down: a garbled listing row adds no run and overturns no answer
ok - fm-control stand-down: a live run that cannot be placed is doubt, not proof of safety
ok - fm-control stand-down: a terminal run whose head never reached this worktree is history, not doubt
ok - fm-control stand-down: corroboration reads the branch's newest run, not its history
ok - fm-control stand-down: the listing adds a live run, and its silence never overturns the branch read
ok - fm-control stand-down: a full window adds no run, and the live row it shows still refuses
ok - fm-control stand-down: an unplaceable live run is named, not misreported as an unanswered check
ok - fm-control stand-down: an unreadable worktree refuses, while a branchless one owns no run
ok - fm-control stand-down: an unacknowledged instruction is handled or withdrawn first
ok - fm-control stand-down: a scout is run-checked like a ship, and its scratch worktree still holds
ok - fm-control stand-down: an ordinary prior exit becomes intentional only through an explicit reversible hold
ok - fm-control repair-worker-state: reality wins over a stale declaration, idempotently
ok - fm-control repair-worker-state: an unprovable record is cleared toward supervision, never toward intent
ok - fm-control repair-worker-state: a vanished endpoint returns to supervision
ok - fm-control: a legacy window label is refused and the exact task id is named
ok - fm-control: an explicit backend endpoint is never a control target
ok - fm-control: an unrecorded task id is refused
ok - fm-control: a record whose endpoint identity names another task is refused
ok - fm-control: a remotely placed secondmate is refused by placement, not by a metadata complaint
ok - fm-control: interrupt, exit, and stand-down lock before task-state resolution
ok - fm-control: the verb list is closed - no raw keys, arbitrary text, or clear verb
ok - fm-control: resume is refused with the determinism reason and the alternative
ok - fm-control: profile and note flags belong to relaunch only
ok - fm-control exit: an already-stopped agent is idempotent success with no bytes sent
ok - fm-control exit: an unprovable tmux endpoint refuses instead of claiming the agent stopped
ok - fm-control interrupt: refuses when no agent is running rather than keying a shell
ok - fm-control exit: an endpoint whose process cannot be attributed refuses
ok - fm-control exit: a busy agent receives interrupt delivery before the exit command
ok - fm-control exit: an idle agent goes straight to its exit command
ok - fm-control exit: retiring a codex incarnation drops busy_gen with the sidecar
ok - fm-control exit: an already-open background-task picker is not typed into
ok - fm-control exit: the Enter that opens the background-task picker is not followed by a confirming Enter
ok - fm-control exit: a picker that renders after the submit returned is named when the stop wait times out
ok - fm-control interrupt: unconfirmed delivery preserves observed busy state
ok - fm-control interrupt: muse confirms cancellation from its session log
ok - fm-control interrupt: postconditions are revalidated after acknowledgement polling
ok - fm-control exit: an interrupt-stopped agent satisfies the gone-state postcondition
ok - fm-control exit: a stubborn agent reports delivered input and an unconfirmed exit
ok - fm-control interrupt: grok reports delivery without claiming cancellation
ok - fm-control interrupt: grok's idle footer does not confirm cancellation
ok - fm-control: a lifecycle command to a secondmate is unmarked and opens no reply expectation
ok - fm-control's arrival leaves fm-send's from-firstmate marking untouched
exit=0 secs=207
Evidence: fm-crew-state suite log

Source: fm-crew-state suite log

ok - captured AXI replacement status replays through crew-state
ok - captured AXI parked status replays through crew-state
ok - captured AXI failed status replays through crew-state
ok - captured capped inventory replays selection, ambiguity, and unavailable lookup
ok - captured status formats reject a synthetic authority transition
ok - captured completed status yields to synthetic subsequent development
ok - captured cancelled run leaves fleet inventory unverified without a failure contradiction
ok - failed-outcome/github/open: terminal delivery uses current disposition
ok - failed-outcome/github/merged: terminal delivery uses current disposition
ok - failed-outcome/github/closed: terminal delivery uses current disposition
ok - failed-outcome/github/unreadable: terminal delivery uses current disposition
ok - failed-outcome/github/skipped: terminal delivery uses current disposition
ok - failed-outcome/github/no-identity: terminal delivery uses current disposition
ok - failed-outcome/gitlab/open: terminal delivery uses current disposition
ok - failed-outcome/gitlab/merged: terminal delivery uses current disposition
ok - failed-outcome/gitlab/closed: terminal delivery uses current disposition
ok - failed-outcome/gitlab/unreadable: terminal delivery uses current disposition
ok - failed-outcome/gitlab/skipped: terminal delivery uses current disposition
ok - failed-outcome/gitlab/no-identity: terminal delivery uses current disposition
ok - failed-outcome/gerrit/open: terminal delivery uses current disposition
ok - failed-outcome/gerrit/merged: terminal delivery uses current disposition
ok - failed-outcome/gerrit/closed: terminal delivery uses current disposition
ok - failed-outcome/gerrit/unreadable: terminal delivery uses current disposition
ok - failed-outcome/gerrit/skipped: terminal delivery uses current disposition
ok - failed-outcome/gerrit/no-identity: terminal delivery uses current disposition
ok - failed-status/github/open: terminal delivery uses current disposition
ok - failed-status/github/merged: terminal delivery uses current disposition
ok - failed-status/github/closed: terminal delivery uses current disposition
ok - failed-status/github/unreadable: terminal delivery uses current disposition
ok - failed-status/github/skipped: terminal delivery uses current disposition
ok - failed-status/github/no-identity: terminal delivery uses current disposition
ok - failed-status/gitlab/open: terminal delivery uses current disposition
ok - failed-status/gitlab/merged: terminal delivery uses current disposition
ok - failed-status/gitlab/closed: terminal delivery uses current disposition
ok - failed-status/gitlab/unreadable: terminal delivery uses current disposition
ok - failed-status/gitlab/skipped: terminal delivery uses current disposition
ok - failed-status/gitlab/no-identity: terminal delivery uses current disposition
ok - failed-status/gerrit/open: terminal delivery uses current disposition
ok - failed-status/gerrit/merged: terminal delivery uses current disposition
ok - failed-status/gerrit/closed: terminal delivery uses current disposition
ok - failed-status/gerrit/unreadable: terminal delivery uses current disposition
ok - failed-status/gerrit/skipped: terminal delivery uses current disposition
ok - failed-status/gerrit/no-identity: terminal delivery uses current disposition
ok - cancelled-outcome/github/open: terminal delivery uses current disposition
ok - cancelled-outcome/github/merged: terminal delivery uses current disposition
ok - cancelled-outcome/github/closed: terminal delivery uses current disposition
ok - cancelled-outcome/github/unreadable: terminal delivery uses current disposition
ok - cancelled-outcome/github/skipped: terminal delivery uses current disposition
ok - cancelled-outcome/github/no-identity: terminal delivery uses current disposition
ok - cancelled-outcome/gitlab/open: terminal delivery uses current disposition
ok - cancelled-outcome/gitlab/merged: terminal delivery uses current disposition
ok - cancelled-outcome/gitlab/closed: terminal delivery uses current disposition
ok - cancelled-outcome/gitlab/unreadable: terminal delivery uses current disposition
ok - cancelled-outcome/gitlab/skipped: terminal delivery uses current disposition
ok - cancelled-outcome/gitlab/no-identity: terminal delivery uses current disposition
ok - cancelled-outcome/gerrit/open: terminal delivery uses current disposition
ok - cancelled-outcome/gerrit/merged: terminal delivery uses current disposition
ok - cancelled-outcome/gerrit/closed: terminal delivery uses current disposition
ok - cancelled-outcome/gerrit/unreadable: terminal delivery uses current disposition
ok - cancelled-outcome/gerrit/skipped: terminal delivery uses current disposition
ok - cancelled-outcome/gerrit/no-identity: terminal delivery uses current disposition
ok - cancelled-status/github/open: terminal delivery uses current disposition
ok - cancelled-status/github/merged: terminal delivery uses current disposition
ok - cancelled-status/github/closed: terminal delivery uses current disposition
ok - cancelled-status/github/unreadable: terminal delivery uses current disposition
ok - cancelled-status/github/skipped: terminal delivery uses current disposition
ok - cancelled-status/github/no-identity: terminal delivery uses current disposition
ok - cancelled-status/gitlab/open: terminal delivery uses current disposition
ok - cancelled-status/gitlab/merged: terminal delivery uses current disposition
ok - cancelled-status/gitlab/closed: terminal delivery uses current disposition
ok - cancelled-status/gitlab/unreadable: terminal delivery uses current disposition
ok - cancelled-status/gitlab/skipped: terminal delivery uses current disposition
ok - cancelled-status/gitlab/no-identity: terminal delivery uses current disposition
ok - cancelled-status/gerrit/open: terminal delivery uses current disposition
ok - cancelled-status/gerrit/merged: terminal delivery uses current disposition
ok - cancelled-status/gerrit/closed: terminal delivery uses current disposition
ok - cancelled-status/gerrit/unreadable: terminal delivery uses current disposition
ok - cancelled-status/gerrit/skipped: terminal delivery uses current disposition
ok - cancelled-status/gerrit/no-identity: terminal delivery uses current disposition
ok - outcome: cancellation without delivery carries no verdict
ok - status: cancellation without delivery carries no verdict
ok - selected: cancellation without delivery carries no verdict
ok - coarse: cancellation without delivery carries no verdict
ok - no-ci-log: cancellation without delivery carries no verdict
ok - red-ci: cancellation without delivery carries no verdict
ok - cancelled-test: cancellation without delivery carries no verdict
ok - skipped-test: cancellation without delivery carries no verdict
ok - synthetic cancelled run leaves fleet inventory unverified without a failure contradiction
ok - cancelled-outcome: terminal delivery reports only observed evidence
ok - cancelled-status: terminal delivery reports only observed evidence
ok - skipped-rebase: terminal delivery reports only observed evidence
ok - cancelled-skipped-rebase: terminal delivery reports only observed evidence
ok - passed: terminal delivery reports only observed evidence
ok - active run-step is authoritative
ok - stale needs-decision over active run is superseded
ok - stale blocked over active run is superseded
ok - daemon/timeout blocked claim over a live fixing run reads as run alive
ok - socket refusal or missing socket over a stale fixing run reports blocked
ok - socket refusal over a terminal attributed run reports blocked
ok - socket-down evidence outranks a live run only while it is the log's latest event
ok - broken-pipe blocker over a live run keeps the plain superseded reading
ok - genuine daemon-down blocked line still reports blocked
ok - a busy secondmate keeps its open blocker until that exact key closes
ok - the most recently opened decision supplies the reported state and detail
ok - ship and scout terminal declarations supersede stale decisions
ok - latest status retains legacy completion events and shared captain matching
ok - latest status subprocess work stays bounded an

... [4060 bytes truncated] ...

ver reads working from rendered footer text
ok - grok still reads working through its isolated rendered-tail fallback
ok - herdr's native busy verdict reads working with no record present
ok - a herdr CLI that fails to answer reads unknown/unreachable, never gone
ok - an alive endpoint whose scrollback read failed stays working
ok - a husk pane (agent gone) still reads gone for reclaim
ok - a mid-tool-call crew stays working because its record outranks herdr's generation state
ok - an idle record with idle agent_status stays not-busy (no regression for a human-blocked agent)
ok - no run + idle pane uses the status-log verb
ok - no run + idle pane parses keyed status syntax
ok - no run + idle pane on a paused: status reports state: paused with its reason
ok - no run + idle pane honors the configured paused verb
ok - a trailing resolved: event does not corrupt state render (idle stays idle)
ok - dead window ignores stale status log
ok - a tmux that fails to answer reads unknown/unreachable, never gone
ok - closed pane still reports a terminal run-step
ok - closed pane still reports an active run-step
ok - no timeout command uses perl bound
ok - scout skips the run lookup
ok - torn-down worktree is handled gracefully
ok - fm-crew-state remote: alive endpoint falls through to the routed status log
ok - fm-crew-state remote: an idle alive endpoint reads alive, never gone or dead
ok - fm-crew-state remote: an unreachable host reads unknown-remote, never gone or dead
ok - fm-crew-state remote: the remote host's own dead verdict is reported truthfully
ok - missing meta is handled gracefully
ok - crew_is_provably_working absorbs a validating crew found only via the runs-list fallback
ok - crew_is_provably_working still surfaces a genuinely stopped crew (safety property preserved)
ok - usage error exits 2
ok - historical same-branch rewritten head is not attributed as current
ok - active run with valid descendant fix head remains current
ok - local work advanced past run head invalidates attribution
ok - pipeline-owned active run binds without head equality and beats the failed row
ok - a genuinely failed run with no later run is not hidden
ok - coarse scan anchors the unresolvable active row instead of falling to an older one
ok - coarse scan with a mismatched anchor stays unknown and lets the pane answer
ok - coarse terminal row at a foreign head is not attributed
ok - an executing run binds regardless of branch_sync state
ok - a parked run keeps the strict head rule without pipeline_owned
ok - run_parked_scalar_gate_running keeps the strict head rule despite its live status word
ok - run_parked_in_gate_block keeps the strict head rule despite its live status word
ok - the exemption never applies to a terminal run
ok - missing run head falls back instead of matching by branch
ok - active fix round with an unfetched pipeline head reads working
ok - unanchored unverifiable active row is attributed because it is live
ok - unresolvable terminal row never reads as current
ok - runs-list continuation attribution works when axi answers another branch
ok - herdr stale registration over a shell-only pane reads agent gone, not alive
ok - herdr stale working record never reports a shell-only pane busy
ok - capped overview retains both competing same-branch run ids
ok - same-branch identity survives both runs falling outside the overview
ok - a capped overview with zero same-branch rows reports absent, not unreadable
ok - no run for this branch beside a live run elsewhere reads absent, not unreadable
ok - the capped inventory reader is bounded by the crew read budget
ok - a repo spelling the inventory does not record reads unreadable
ok - a linked worktree green PR in merge monitoring reads held for merge
ok - complete inventory preserves the replacement gate without writes
ok - missing complete-inventory failure reports unknown
ok - corrupt complete-inventory failure reports unknown
ok - schema complete-inventory failure reports unknown
ok - repo complete-inventory failure reports unknown
ok - count complete-inventory failure reports unknown
ok - norepo complete-inventory failure reports unknown
ok - R6 complete selection ignores unrelated branch semantics
ok - R6 requested branches use exact identity without a whitelist
ok - R6 capped inventory ignores unrelated semantics and names both ids
ok - R6 complete identity lookup precedes partial-row semantic rejection
ok - R6 capped inventory preserves quoted requested-branch identity
ok - R6 structural completeness and requested-run validation remain enforced
ok - R5 complete inventory without Python keeps the replacement gate
ok - R5 complete ambiguity without Python names both ids
ok - R5 capped lookup without Python preserves available ids
ok - R5 capped lookup without SQLite support preserves available ids
ok - R1 both directions of inventory liveness disagreement read unknown
ok - R2 uninitialized busy workers retain pane reporting
ok - R2 uninitialized idle workers retain status reporting
ok - R3 historical inventory yields to the current busy pane
ok - R3 historical inventory yields to current worker status
ok - superseded cancelled run preserves the replacement review gate
ok - a live rebased run beats an older failed run at the local head
ok - pending run with a rebased head reads working
ok - running run with a rebased head reads working
ok - legacy live rebased run is authoritative over an older failed row
ok - legacy surface binds a fixing run at a rebased head
ok - legacy surface binds a ci run at a rebased head
ok - an unproven record at a diverged head does not answer for the crew
ok - an unproven record with a dead daemon never overrides a busy pane
ok - an ordinary blocked tip over a coarse live row keeps the superseded reading
ok - a socket-refused blocker survives the dead-daemon verdict
ok - an ordinary blocked tip survives the dead-daemon verdict
ok - a parked gate survives a dead daemon with its findings intact
ok - an unproven record at a diverged head does not answer on the selected route
ok - a live record at a diverged head binds while the daemon answers
ok - the anchored continuation binds while the daemon answers
ok - a head-tied coarse record keeps its working reading and its original note
ok - a coarse failed record with a dead daemon reads unknown
ok - the selected-run anchored continuation reports the dead daemon, not an identity failure
ok - the selected-run anchored continuation binds while the daemon answers
ok - the selected route keeps an anchored parked run's gate with a dead daemon
ok - an open decision survives the dead-daemon verdict on the selected route
ok - a coarse pending ledger word reads unknown
ok - an unanswered probe never turns a failed coarse record into a gate
ok - the selected-route dead-daemon verdict names the run once
ok - a head-tied row reads working when axi names the self run
ok - a head-tied row reads working when axi names the other run
ok - a head-tied coarse row is exempt even when the record head diverged
ok - an unrecognised ledger word keeps the ordinary supersede note
ok - an unanswered daemon probe leaves a live rebased run bound
ok - a head-tied coarse live row is exempt from the dead-daemon verdict
ok - a coarse live row over an open decision keeps the original supersede note
ok - a coarse live row at a rebased head is not attributed
ok - a terminal run at a diverged head keeps the strict head rule
ok - competing live runs report unknown with both run ids
ok - newer failed run remains failed beside an older live run
ok - missing run selection reports unknown with candidate ids
ok - wrong-id run selection reports unknown with candidate ids
ok - wrong-branch run selection reports unknown with candidate ids
ok - wrong-head run selection reports unknown with candidate ids
ok - missing-status run selection reports unknown with candidate ids
ok - malformed-table run selection reports unknown with candidate ids
ok - inventory-error run selection reports unknown with candidate ids
ok - selected-error run selection reports unknown with candidate ids
ok - legacy conflicting run records report unknown
all fm-crew-state tests passed
exit=0 secs=232
Evidence: Key live output
$ bin/fm-control.sh t1 stand-down
stood-down t1 harness=claude backend=tmux endpoint=fmlab:fm-t1
$ bin/fm-crew-state.sh t1
state: parked · source: worker-state · worker deliberately stood down
$ bin/fm-send.sh t1 hello
error: task t1 deliberately has no worker; relaunch it before sending an instruction
(after kill-window) state: unknown · source: worker-state · stood down but its endpoint is gone: fmlab:fm-t1

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

⏭️ **Rebase** - skipped
  • ⚠️ AGENTS.md - merge conflict rebasing onto origin/main
  • ⚠️ bin/fm-control.sh - merge conflict rebasing onto origin/main
  • ⚠️ bin/fm-teardown.sh - merge conflict rebasing onto origin/main
  • ⚠️ docs/agent-control.md - merge conflict rebasing onto origin/main
  • ⚠️ docs/architecture.md - merge conflict rebasing onto origin/main
  • ⚠️ docs/configuration.md - merge conflict rebasing onto origin/main
  • ⚠️ tests/fm-control-relaunch.test.sh - merge conflict rebasing onto origin/main
  • ⚠️ tests/fm-crew-state.test.sh - merge conflict rebasing onto origin/main
✅ **Review** - passed

✅ No issues found.

✅ **Test** - passed

✅ No issues found.

  • Live validation: ✅ go - 8 of 10 scenarios driven live against the product
Scenario Result Live Evidence
Operator stands down a live, idle Claude worker; the agent stops, the pane stays, and an exact worker-state record is written ✅ pass live live-stand-down.log S2: rc=0 'stood-down t1', the pane command changed from 2.1.296 to zsh, and the record holds schema=1, task_id=t1, endpoint=fmlab:fm-t1, state=stood-down
Crew-state reports the stood-down task as parked, and a repeat stand-down does nothing more ✅ pass live live-stand-down.log S3/S3b: 'state: parked · source: worker-state'; 'already-stood-down t1'
Real watcher raises no stale or wedge alarm for the stood-down task but still alarms for a dead worker with no declaration ✅ pass live live-watch.log: no .hash-fmlab_fm-t1 file and no t1 stale wake in 4 cycles; 'stale: fmlab:fm-t2' was emitted and queued
fm-send refuses an instruction to a stood-down task and creates no unread inbox record ✅ pass live live-stand-down.log S4: rc=1 'task t1 deliberately has no worker'; inbox has nothing pending
Adversarial: stand-down refuses to turn a worker that is already dead, with no declared hold, into an intentional hold ✅ pass live live-stand-down.log S5: rc=1 'already dead without a stand-down record'; no record written; crew-state is not parked
A declared hold ('paused:' line) lets stand-down record a worker that is already dead ✅ pass live live-stand-down.log S6: 'stood-down t2', crew-state parked
Vanished endpoint behind a stood-down record stays visible as unknown, and repair clears the record ✅ pass live live-stand-down.log S7/S8: repair kept the record while the endpoint was dead ('intact'); after kill-window, crew-state reported 'stood down but its endpoint is gone'; repair returned 'cleared-unprove…
Adversarial: a live worker behind an old stood-down record is not hidden, is not refused input, and repair clears the record ✅ pass live live-stand-down.log S9: crew-state 'endpoint has a live worker'; fm-send rc=0 and Claude received the instruction (live-t2-pane-after-send.txt); repair returned 'cleared-live-worker'
Relaunch from a stood-down task restarts in the preserved worktree and clears only the declaration ⏸️ untested no The prior payload did not establish a live result. It was not driven live because that needs a full fm-spawn of a second Claude worker in the lab, and the previous run had already exceeded its time bu…
Stand-down refuses while the branch has an in-flight no-mistakes run, or while the run check cannot be answered ⏸️ untested no The prior payload did not establish a live result. A live test needs a real in-flight no-mistakes run registered for a lab repository. Starting one is a pipeline-control action that this gate phase mu…
  • bash tests/fm-control.test.sh (exit 0)
  • bash tests/fm-crew-state.test.sh (exit 0)
  • bash tests/fm-control-relaunch.test.sh (exit 0)
  • bash tests/fm-send-inbox.test.sh (exit 0)
  • bash tests/fm-task-inbox.test.sh (exit 0)
  • bash tests/fm-watch-triage.test.sh (exit 0, including both stood-down watcher tests)
  • Live lab: bin/fm-lab-home.sh create plus a private tmux -L fm-lab socket, with real claude 2.1.296 in pane fm-t1 and a plain shell in fm-t2
  • Live: bin/fm-control.sh t1 stand-down, run once and then again
  • Live: bin/fm-crew-state.sh t1 / t2 before and after
  • Live: bin/fm-send.sh t1 hello against the stood-down task
  • Live: bin/fm-control.sh t2 stand-down with no declared hold, then again after a paused: line
  • Live: 4 cycles of real bin/fm-watch.sh with bin/fm-wake-drain.sh acknowledgements
  • Live: bin/fm-control.sh t1 repair-worker-state with the endpoint dead, then after kill-window
  • Live: manual claude relaunch in fm-t2 behind a stood-down record, then fm-crew-state, fm-send and repair-worker-state
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

@greptile-apps

greptile-apps Bot commented Aug 29, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 4/5

[Medium risk] Adds worker lifecycle control and stand-down state tracking.

The PR does not appear safe to merge until the waiting-worker inbox retry can proceed when an older ordinary instruction remains escalated.

Reviews (10) · Last reviewed commit: "Merge upstream main into stood-down work..."

@kunchenguid

Copy link
Copy Markdown
Owner

Speaking as Kun's firstmate:

Reviewed HEAD d402739060cecca80622526f652da54954ecfc1f vs main. Whole thread read (body, Greptile 5/5, no human reviews). Files: new bin/fm-worker-state-lib.sh, bin/fm-control.sh (stand-down / repair-worker-state), crew-state / nm-run-lib / send / spawn / teardown / watch, docs, tests. No .github/workflows/*. AgardnerAU is not blocked. Not disguised security. Fail-closed: unproven records clear toward supervision; live agents stay in wedge detection; Herdr relaunch remains; stand-down is tmux-only.

Contract-class: new-default. Unconfigured fleet now has first-class stand-down / repair-worker-state verbs, a durable worker-state record, watcher exemption for proven holds, and fm-send refusal to a proven worker-free task. That is a new default lifecycle path, not a restore of a promised existing hold.

VISION (per rule):

  • One captain, one interface — aligns (stops false dead-worker wedges on intentional holds).
  • Authority is explicit — tension (new capability ships as default verbs/docs, not an opt-in flag).
  • Scripts own the mechanics — aligns (deterministic record + classifiers; no agent judgment mixed in).
  • A restart is a non-event — aligns (durable endpoint-bound record; interrupted standing-down stays supervised).
  • Delegation with a spine — aligns (refuses unproven holds; does not cancel in-flight runs).
  • The fleet outlives any vendor — aligns (tmux-only worker-state; Herdr ordinary relaunch kept).
  • Scope — aligns (lifecycle supervision, not merge policy).

Attestation HTML binds head_sha b8a8bd077c4aa9401ccfc56420b2b8c9e071ce5c, not this HEAD. no-mistakes is blocking. Fork CI approved this pass: 33250636532 (CI), 33250636533 (Require no-mistakes). Waiting on the author to rebind attestation. Not with the captain (not otherwise ready). No auto-merge. I am not opening a competing PR.

@AgardnerAU
AgardnerAU force-pushed the fm/fm-stooddown-worker-false-wedge-alarms branch from d402739 to 562d760 Compare August 30, 2026 02:39
@AgardnerAU AgardnerAU changed the title fix(bin): distinguish intentional stand-downs from dead workers feat(bin): represent deliberately stood-down workers Aug 30, 2026
@kunchenguid

Copy link
Copy Markdown
Owner

Speaking as Kun's firstmate: re-triage after newer author activity (HEAD moved since 2026-08-29 stamp).

Outcome: waiting-ci (attestation now MATCH; first-time fork CI re-approved this pass for new HEAD and is pending)

contract-class: new-default — unconfigured fleet gains first-class stand-down / repair-worker-state verbs, durable worker-state records, watcher exemption for proven holds, and fm-send refusal to a proven worker-free task. Still not a restore of a previously specified hold path (prior class stands).

VISION.md

  • One captain, one interface: pass — stops false dead-worker wedges on intentional holds.
  • Authority explicit never inferred: tension — new default lifecycle verbs/docs, not an opt-in flag.
  • Scripts own mechanics: pass — deterministic worker-state record + classifiers.
  • Restart non-event: pass — durable endpoint-bound record; interrupted standing-down stays supervised.
  • Delegation with spine: pass — refuses unproven holds; does not cancel in-flight NM runs.
  • Fleet outlives vendor: pass — tmux-only worker-state; Herdr ordinary relaunch kept.
  • Scope: pass — lifecycle supervision, not merge policy.

Attestation: MATCH 2804abbd59f9f066d10c1e323b0acbe768c6cbd1 (prior stamp had MISMATCH) · CI/NM: pending after approve (35379109555, 35379109799) · Mergeable: MERGEABLE/UNSTABLE · workflow-zero: yes · Firstmate-flag: no (not otherwise ready — defer default-behavior card) · security-fyi: no · 14d-stale: no (author push 2026-09-18)

@AgardnerAU

Copy link
Copy Markdown
Author

CI run 35379109555, job 105717538312, failed on one assertion: budget exhaustion (hang) never started the observation in tests/fm-contributions.test.sh, preceded by grep: .../budget-hang/forge/calls: No such file or directory. The other 17 jobs and the body-compliance run passed.

I could not reproduce it on either side. Against this branch head 2804abbd and against unmodified main at 9bc051ff, in separate clones: 5 sequential runs plus 8 concurrent runs each, so 13 runs per tree and 26 in total, all passing.

On the mechanism: test_budget_exhaustion_keeps_prior_record hang runs the poll under FM_CONTRIBUTIONS_BUDGET=1 against a fixture gh that sleeps 4 seconds. That fixture appends its arguments to $FORGE/calls before sleeping, and the assertion greps that file, so it holds only if the subprocess starts and completes its first write inside the one-second budget. If the bounded call ends it first, the file is never created at all — which matches the symptom here, a missing file rather than wrong contents. The sibling exhaust case, which has no sleep, passed.

One thing you should weigh yourselves: this branch adds about 100 lines of tests to tests/fm-control-relaunch.test.sh, which runs in the same shard (portable serial 7) and took 64 seconds there, in a shard totalling 512 seconds. Added test time cannot introduce the race, since it is inherent to the fixture above, but on a contended shard it can plausibly change the timing that decides it.

Would you be willing to re-run that job? A re-run is the cheapest way to tell a timing-dependent failure from a real regression.

@AgardnerAU

Copy link
Copy Markdown
Author

Follow-up to my previous comment: I can now reproduce that failure deterministically, which turns the earlier "could not reproduce" into a causal explanation.

The fixture gh in test_budget_exhaustion_keeps_prior_record hang appends its arguments to $FORGE/calls on its third line and then sleeps 4 seconds, while the poll runs under FM_CONTRIBUTIONS_BUDGET=1. The assertion therefore holds only if that subprocess reaches its third line inside one second. Injecting a 2-second startup delay ahead of that append — what a contended runner does to process startup — and changing no assertion, produces the reported symptom bit-for-bit:

grep: /.../budget-hang/forge/calls: No such file or directory
not ok - budget exhaustion (hang) never started the observation

A missing file rather than wrong contents, and the same assertion text. That is a pre-existing race in the fixture, independent of this change.

To be straight about what does not support that conclusion: I also ran the suite at 24-way concurrency, and all 24 runs failed — but on different assertions (1 contribution regressions, and watcher did not surface the new contribution signal). None of them produced this job's symptom. That run suggests the suite is timing-sensitive under load generally, but it is not evidence for this specific failure and I am not offering it as such.

Could you re-run job 105717538312? I cannot do it myself — gh run rerun reports that it needs admin rights on this repository. I would rather not push a commit to force a fresh run either: when this branch was pushed at 18:15 both workflow runs went to action_required and waited for a maintainer anyway, and a new push would also move the head that the attestation in the PR body is bound to, which is the mismatch this update set out to fix.

Everything else is green: 18 of 19 checks, including the body-compliance run.

@kunchenguid

Copy link
Copy Markdown
Owner

Speaking as Kun's firstmate: re-triage after newer author activity (HEAD still 2804abbd…; author diagnosed CI failure).

HEAD 2804abbd59f9f066d10c1e323b0acbe768c6cbd1. MERGEABLE/UNSTABLE vs main 1bb72cc5f880. Author AgardnerAU is not blocked.

Attestation: MATCH. CI run 35379109555 FAILURE (Behavior portable serial 7 / tests/fm-contributions.test.sh budget-hang assertion; author reproduced and explained fixture race). NM body-compliance 35379109799 SUCCESS. Prior workflow approval stands; not re-approving a red tip.

Contract-class: new-default (prior class stands). Unconfigured fleet gains first-class stand-down / repair-worker-state, durable *.worker-state, watcher exemption for proven holds, and fm-send refusal to a proven worker-free task. That is a new default lifecycle surface, not restore of a previously specified hold path. No auto-merge.

VISION.md (per rule)

  • One captain, one interface: aligns — stops false dead-worker wedges on intentional holds.
  • Authority explicit never inferred: tension — new default lifecycle verbs/docs without an opt-in gate (hence new-default, not restore).
  • Scripts own mechanics: aligns — deterministic worker-state lib + control verbs.
  • A restart is a non-event: aligns — durable stand-down record survives session death.
  • Delegation with a spine: aligns — explicit hold vs dead-worker distinction.
  • Fleet outlives vendors: aligns — tmux-only lifecycle; Herdr relaunch preserved.
  • Scope: aligns — command-layer supervision, not workshop.

Outcome: waiting-author (CI red; author owns the contributions budget-hang fix). Firstmate flag: no — not otherwise ready while CI-red. Security FYI: none (fail-closed; live agents stay in wedge detection).

…r-false-wedge-alarms

# Conflicts:
#	bin/fm-control.sh
#	docs/agent-control.md
#	docs/scripts.md
test_budget_exhaustion_keeps_prior_record's `hang` mode faulted the first
forge call of the observation, so the one line proving the observation had
started was written by the very process the budget deadline was about to
kill. With a one-second budget, a contended runner could spend the whole
bound spawning the fixture, leaving $FORGE/calls absent and failing
"budget exhaustion (hang) never started the observation".

Move the hang onto a later call so the calls that prove the observation
started are ordinary completed reads, and raise the budget to five seconds,
which is still `-le 5` and so still classifies a killed read as budget
exhaustion rather than an unavailable forge.

The assertions are unchanged. Record preservation remains the proof that
exhaustion actually happened, so the case cannot pass vacuously: disabling
the hang fault still fails the test.
@AgardnerAU

Copy link
Copy Markdown
Author

Rebased onto current main and ready for CI.

Three conflicts, additive on both sides: main added the endpoint-absence proof shared by exit and relaunch, this branch adds the stand-down and repair-worker-state verbs. Both survive in bin/fm-control.sh, docs/agent-control.md and docs/scripts.md. Nothing this PR promises changed.

One change outside the original scope, flagged because it is a reviewer-visible claim rather than a merge decision. CI failed at "budget exhaustion (hang) never started the observation" in tests/fm-contributions.test.sh, preceded by grep: .../budget-hang/forge/calls: No such file or directory. The fault is in the fixture, not this branch: hang targeted the first forge call, so the line proving the observation had started was written by the very process the one-second budget was about to kill, and a slow process start on a contended runner consumed the whole bound.

The hang now targets a later call, so that evidence comes from completed reads rather than the read being killed, and the budget is five seconds - still -le 5, so a killed read is still budget exhaustion rather than an unavailable forge. No assertion was removed or relaxed; record preservation still proves exhaustion occurred, so the case cannot pass vacuously. Verified by reproducing the failure with an injected fixture startup delay, confirming the fixed test survives a 3-second delay on every fixture call, and confirming it still fails with the hang fault disabled.

I believe the pattern is wider and have left the rest alone: the FORGE_LATENCY=3 case has roughly two seconds of headroom against a five-second bound, and "eight 3-second PR reads complete fresh within one 20-second poll cycle" is timing-sensitive by construction. An earlier run at 24-way concurrency failed on different assertions. Those are for separate follow-up.

@AgardnerAU AgardnerAU changed the title feat(bin): represent deliberately stood-down workers fix(bin): stop false wedge alarms for deliberately stood-down workers Sep 27, 2026
@AgardnerAU

Copy link
Copy Markdown
Author

Updated with current upstream main. The PR is mergeable and ready for CI once the fork workflow runs are approved.

@AgardnerAU

Copy link
Copy Markdown
Author

Refreshed onto current upstream main at 9c15ae1; the two conflicts (operational-home-layout state list, teardown rm list) were additive and both sides are kept. The budget-hang fixture fix from the earlier refresh is carried unchanged (upstream main has not changed that test); tests/fm-contributions.test.sh passed 3/3 runs idle and 5/5 under heavy CPU load locally. Mergeable and ready for CI - the CI and Require no-mistakes runs are awaiting workflow approval.

…r-false-wedge-alarms

# Conflicts:
#	bin/fm-control.sh
#	tests/fm-contributions.test.sh
@AgardnerAU

Copy link
Copy Markdown
Author

Merged current upstream main into this branch (head 9dc61a0), resolving the conflicts in bin/fm-control.sh and tests/fm-contributions.test.sh; the budget-hang fixture fix is kept. The branch is up to date with main and ready for CI; the workflow runs are waiting for maintainer approval.

@kunchenguid

Copy link
Copy Markdown
Owner

Speaking as Kun's firstmate (OSS triage, not the captain).

Thanks @AgardnerAU. My 09-19 stamp was waiting-author, because CI was red on the contributions budget-hang test at 2804abbd. Since then you've landed the fixture fix and merged main. The new head is 9dc61a0d, attestation matches it, and it merges cleanly with main fb75c1f9.

I re-reviewed the tip diff. There are no workflow or secret changes, so I approved the fork runs: CI 38010402948 and Require no-mistakes 38010402921.

Contract-class: new-default (unchanged). An unconfigured fleet gains first-class stand-down and repair-worker-state verbs, durable *.worker-state records, a watcher exemption for proven holds, and an fm-send refusal. That's a new default lifecycle surface, not a broken specified path being fixed.

VISION (unchanged):

  • One captain/one interface: aligns (no more false wedge alarms).
  • Authority: in tension, because the new default verbs have no opt-in gate. That's why this is new-default.
  • Scripts own mechanics: aligns.
  • Restart: aligns (durable record).
  • Delegation: aligns.
  • Vendor: aligns.
  • Scope: aligns.

Outcome: waiting-ci. The author-side blocker is cleared. Once CI and no-mistakes are green, this goes to the captain to merge or leave. It won't auto-merge. Nothing is needed from you right now.

…r-false-wedge-alarms

# Conflicts:
#	tests/fm-crew-state.test.sh
@AgardnerAU

Copy link
Copy Markdown
Author

Merged current upstream main into this branch (head 876c626); the one test-file conflict is resolved and the branch has no conflicts with main. The affected tests pass locally. Ready for CI - the fork workflow runs are waiting for maintainer approval.

@kunchenguid

Copy link
Copy Markdown
Owner

Speaking as Kun's firstmate: thanks @AgardnerAU — tip 876c6265 merges current main; conflict resolved; attestation MATCH. Diff re-reviewed (stand-down / repair-worker-state, worker-state lib, watch exemption, send refuse, relaunch clear). Workflow-zero. Approved fork runs for this tip: CI 38477789539, Require no-mistakes 38477789751.

Contract-class: new-default (unchanged). Unconfigured fleet still gains first-class stand-down verbs, durable *.worker-state, watcher exemption for proven holds, and fm-send refusal — new default lifecycle surface, not restore.

VISION: one-interface / scripts / restart / spine / vendor / scope align; authority in tension (no opt-in gate) — hence new-default.

Outcome: waiting-ci. Main is red; no merge. When CI + no-mistakes are green on a green main, this goes to the captain to merge or leave (not auto-merge). Live 8/10 noted (relaunch + in-flight NM covered by suites). Nothing needed from you right now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants